package demoMod.stsconsole.characters;

import demoMod.stsconsole.ConsoleEntry;
import demoMod.stsconsole.cards.AbstractCard;
import demoMod.stsconsole.core.AbstractCreature;
import demoMod.stsconsole.dungeons.AbstractDungeon;
import demoMod.stsconsole.monsters.AbstractMonster;
import demoMod.stsconsole.potions.AbstractPotion;
import demoMod.stsconsole.relics.AbstractRelic;
import demoMod.stsconsole.saves.InstanceDataSaver;
import demoMod.stsconsole.helpers.*;
import demoMod.stsconsole.interfaces.*;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

public abstract class AbstractPlayer extends AbstractCreature implements CustomSavable<Map<String, Object>> {
    private final List<AbstractRelic> relics = new ArrayList<>();
    private final List<AbstractPotion> potions = new ArrayList<>();
    public List<String> commonStrings;
    public List<AbstractCard> masterDeck = new ArrayList<>();

    public List<AbstractCard> drawPile = new ArrayList<>();
    public List<AbstractCard> hand = new ArrayList<>();
    public List<AbstractCard> discardPile = new ArrayList<>();
    public List<AbstractCard> exhaustPile = new ArrayList<>();

    public String descriptions;
    public String spireHeartText;
    public String cityVampireText;

    public int orbNum; //默认充能球栏位数
    public int maxPotionSlot = 3; //默认药水栏位数
    public int numCardsDrawPerTurn; //每回合抽牌数
    public int numEnergyGainPerTurn; //每回合初始能量数
    public int currEnergy;
    public int maxHandSize = 10; //最大手牌数
    public int cardPurgeServiceCount = 0; //删牌服务购买次数

    public AbstractPlayer(String id) {
        super(id);
        onReloadStrings();
    }

    public List<AbstractRelic> getRelics() {
        return relics;
    }

    public AbstractRelic getRelic(String id) {
        for (AbstractRelic relic : relics) {
            if (relic.id.equals(id)) {
                return relic;
            }
        }
        return null;
    }

    public void obtainRelic(String id) {
        AbstractRelic relic = RelicLibrary.getRelic(id).makeCopy();
        SubscriberReturnValueSaver saver = new SubscriberReturnValueSaver();
        saver.booleanValue = true;
        ConsoleEntry.triggerSubscribers(RelicObtainSubscriber.class, "onEquip",
                new Class[]{AbstractRelic.class},
                new Object[]{relic},
                (method, params, subscriber) -> {
                    try {
                        saver.booleanValue = saver.booleanValue && (boolean) method.invoke(subscriber, params);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
        );
        if (saver.booleanValue) {
            this.relics.add(relic);
        } else {
            relic.onRemove();
        }
    }

    public void loseRelic(String id) {
        if (hasRelic(id)) {
            AbstractRelic relic = getRelic(id);
            SubscriberReturnValueSaver saver = new SubscriberReturnValueSaver();
            saver.booleanValue = true;
            ConsoleEntry.triggerSubscribers(RelicLoseSubscriber.class, "onUnequip",
                    new Class[]{AbstractRelic.class},
                    new Object[]{relic},
                    (method, params, subscriber) -> {
                        try {
                            saver.booleanValue = saver.booleanValue && (boolean) method.invoke(subscriber, params);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
            );
            if (saver.booleanValue) {
                this.relics.remove(relic);
                relic.onRemove();
            }
        }
    }

    public void addCardToMasterDeck(AbstractCard card) {
        this.masterDeck.add(card);
        ConsoleEntry.triggerSubscribers(MasterDeckChangeSubscriber.class, "onAddToMasterDeck", new Class[]{AbstractCard.class}, new Object[]{card});
    }

    public void removeCardFromMasterDeck(AbstractCard card) {
        this.masterDeck.remove(card);
        ConsoleEntry.triggerSubscribers(MasterDeckChangeSubscriber.class, "onRemoveFromMasterDeck", new Class[]{AbstractCard.class}, new Object[]{card});
    }

    public void obtainPotion(AbstractPotion potion) {
        if (this.potions.size() < this.maxPotionSlot) {
            ConsoleEntry.triggerSubscribers(PotionObtainSubscriber.class, "onObtainPotion", new Class[]{AbstractPotion.class}, new Object[]{potion});
            this.potions.add(potion);
        }
    }

    public boolean hasPotion(String potionId) {
        for (AbstractPotion potion : this.potions) {
            if (potion.id.equals(potionId)) {
                return true;
            }
        }
        return false;
    }

    public boolean hasPotion(AbstractPotion potionInstance) {
        return this.potions.contains(potionInstance);
    }

    public List<AbstractPotion> getPotions() {
        return this.potions;
    }

    public void destroyPotion(AbstractPotion potion) {
        this.potions.remove(potion);
        potion.onRemove();
    }

    public void gainEnergy(int amount) {
        ConsoleEntry.triggerSubscribers(EnergyGainSubscriber.class, "atEnergyGain", new Class[]{int.class}, new Object[]{amount});
        this.currEnergy += amount;
    }

    public void exhaustCard(AbstractCard card, List<AbstractCard> pile) {
        if (pile.contains(card)) {
            ConsoleEntry.triggerSubscribers(ExhaustCardSubscriber.class, "onExhaustCard", new Class[]{AbstractCard.class}, new Object[]{card});
            pile.remove(card);
            AbstractDungeon.player.exhaustPile.add(card);
        }
    }

    public void draw(int amount) {
        draw(amount, false);
    }

    public void draw(int amount, boolean isTurnStart) {
        SubscriberReturnValueSaver returnValueSaver = new SubscriberReturnValueSaver();
        returnValueSaver.intValue = amount;
        ConsoleEntry.triggerSubscribers(PreDrawSubscriber.class,
                "beforeCardDraw",
                new Class[]{int.class, boolean.class},
                new Object[]{amount, isTurnStart},
                (method, params, subscriber) -> {
                    try {
                        params[0] = method.invoke(subscriber, params);
                        returnValueSaver.intValue = (int) params[0];
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
        );
        amount = returnValueSaver.intValue;
        for (int i=0;i<amount;i++) {
            if (!draw()) {
                if (this.hand.size() >= this.maxHandSize && this.drawPile.size() == 0) {
                    this.talk(commonStrings.get(0));
                }
                break;
            }
        }
    }

    private boolean draw() {
        if (this.drawPile.size() == 0) {
            this.talk(commonStrings.get(4));
            if (this.discardPile.size() > 0 && this.hand.size() < this.maxHandSize) {
                shuffle();
            } else {
                return false;
            }
        }
        if (this.hand.size() >= this.maxHandSize) {
            this.talk(commonStrings.get(0));
            return false;
        }
        AbstractCard card = this.drawPile.remove(0);
        this.hand.add(card);
        this.talk(commonStrings.get(6) + card.name);
        ConsoleEntry.triggerSubscribers(PostDrawSubscriber.class, "onCardDraw", new Class[]{AbstractCard.class}, new Object[]{card});
        return true;
    }

    public void shuffle() {
        this.talk(commonStrings.get(5));
        this.drawPile.addAll(this.discardPile);
        this.discardPile.clear();
        Collections.shuffle(this.drawPile, AbstractDungeon.cardRng);
        ConsoleEntry.triggerSubscribers(PostShuffleSubscriber.class, "onShuffle");
    }

    public void discard(int index) {
        if (index < this.hand.size()) {
            AbstractCard card = this.hand.get(index);
            this.discardPile.add(this.hand.remove(index));
            ConsoleEntry.triggerSubscribers(PostDiscardSubscriber.class, "onCardDiscard", new Class[]{AbstractCard.class}, new Object[]{card});
        }
    }

    public void discardAll() {
        this.discardPile.addAll(this.hand.stream().filter(card -> !card.retain).collect(Collectors.toList()));
        this.hand.removeAll(this.hand.stream().filter(card -> !card.retain).collect(Collectors.toList()));
    }

    public void makeTempCardsInHand(AbstractCard card, int amount) {
        makeTempCardsInPile(card, this.hand, amount, false, true);
    }

    public void makeTempCardsInDrawPile(AbstractCard card, int amount, boolean randomSlot, boolean toBottom) {
        makeTempCardsInPile(card, this.drawPile, amount, randomSlot, toBottom);
    }

    public void makeTempCardsInDiscardPile(AbstractCard card, int amount) {
        makeTempCardsInPile(card, this.discardPile, amount, false, true);
    }

    private void makeTempCardsInPile(AbstractCard card, List<AbstractCard> cardPile, int amount, boolean randomSlot, boolean toBottom) {
        ConsoleEntry.triggerSubscribers(AddTempCardsSubscriber.class, "preAddTempCards", new Class[]{AbstractCard.class}, new Object[]{card});
        if (cardPile == this.hand && cardPile.size() >= this.maxHandSize) {
            this.talk(commonStrings.get(0));
            this.discardPile.add(card);
        } else {
            if (randomSlot) {
                cardPile.add(AbstractDungeon.cardRng.nextInt(cardPile.size()), card);
            } else {
                if (toBottom) {
                    cardPile.add(card);
                } else {
                    cardPile.add(0, card);
                }
            }
        }
        for (int i=0;i<amount - 1;i++) {
            if (cardPile == this.hand && cardPile.size() >= this.maxHandSize) {
                this.talk(commonStrings.get(0));
                this.discardPile.add(card.makeStatEquivalentCopy());
            } else {
                if (randomSlot) {
                    cardPile.add(AbstractDungeon.cardRng.nextInt(cardPile.size()), card.makeStatEquivalentCopy());
                } else {
                    if (toBottom) {
                        cardPile.add(card.makeStatEquivalentCopy());
                    } else {
                        cardPile.add(0, card.makeStatEquivalentCopy());
                    }

                }
            }
        }
    }

    public void damageAllEnemies(DamageInfo info) {
        for (AbstractCreature creature : AbstractDungeon.currDungeon.currMapNode.room.monsters.monsters) {
            if (creature instanceof AbstractMonster && !creature.isDeadOrEscaped()) {
                info.applyPowers(creature);
                creature.damage(info);
            }
        }
        ConsoleEntry.triggerSubscribers(DamageAllEnemiesSubscriber.class, "postDamageAllEnemies");
    }

    @Override
    public void damage(DamageInfo info) {
        ConsoleEntry.triggerSubscribers(DamageSubscriber.class,
                "onAttack",
                new Class[]{float.class, DamageInfo.class, AbstractCreature.class},
                new Object[]{info.output, info, this}
        );
        this.talk(commonStrings.get(7));
        if (info.output > 0) {
            info.output = (int) info.output;
            if (info.type == DamageInfo.DamageType.HP_LOSS) {
                this.lastDamageTaken = Math.min((int) info.output, this.currentHealth);
                this.currentHealth -= info.output;
                this.talk(String.format(this.commonStrings.get(8), (int) info.output));
            } else {
                float tmp = info.output;
                info.output -= this.currentBlock;
                this.currentBlock -= tmp;
                if (this.currentBlock < 0) this.currentBlock = 0;
                if (info.output > 0) {
                    this.lastDamageTaken = Math.min((int) info.output, this.currentHealth);
                    this.currentHealth -= info.output;
                    this.talk(String.format(this.commonStrings.get(8), (int) info.output));
                } else {
                    this.talk(commonStrings.get(9));
                }
            }
            if (info.output > 0) {
                ConsoleEntry.triggerSubscribers(InflictSubscriber.class, "onInflictDamage", new Class[]{DamageInfo.class, AbstractCreature.class}, new Object[]{info, this});
            }
            if (this.currentHealth <= 0) {
                this.currentHealth = 0;
                ConsoleEntry.triggerSubscribers(ResurrectSubscriber.class, "onResurrect", new Class[]{AbstractCreature.class}, new Object[]{this});
                if (this.currentHealth <= 0) {
                    this.die();
                }
            }
        } else {
            this.talk(this.commonStrings.get(9));
        }
    }

    @Override
    public void gainBlock(int blockAmount) {
        super.gainBlock(blockAmount);
        this.talk(String.format(commonStrings.get(10), blockAmount));
    }

    @Override
    public void die() {
        this.isDead = true;
        this.onRemove();
        while (!PromptHelper.getCurrentHierarchy().equals("")) {
            PromptHelper.backwardPrompt();
        }
        SaveHelper.deleteSave();
    }

    @Override
    public void escape() {
        this.isEscaped = true;
        AbstractDungeon.currDungeon.currMapNode.room.combatManager.endCombat(false);
    }

    @Override
    public void onReloadStrings() {
        List<String> characterStrings = LocalizedStrings.getCharacterStrings(this.id);
        this.name = characterStrings.get(0);
        this.descriptions = characterStrings.get(1);
        this.spireHeartText = characterStrings.get(2);
        this.cityVampireText = characterStrings.get(3);
        this.commonStrings = LocalizedStrings.getCharacterStrings("AbstractPlayer");
    }

    public List<AbstractCard> getUpgradableCards() {
        List<AbstractCard> ret = new ArrayList<>();
        for (AbstractCard card : masterDeck) {
            if (!card.upgraded) ret.add(card);
        }
        return ret;
    }

    public abstract List<String> getStartingDeck();

    public abstract List<String> getStartingRelics();

    public abstract AbstractCard getCharacterBasicCard();

    public abstract AbstractCard.CardColor getCardColor();

    public abstract AbstractRelic.RelicColor getRelicColor();

    public abstract AbstractPotion.PotionColor getPotionColor();

    @Override
    public Map<String, Object> onSave() {
        Map<String, Object> ret = new HashMap<>();
        ret.put("id", this.id);
        ret.put("hp", this.currentHealth);
        ret.put("maxHp", this.maxHealth);
        ret.put("gold", this.gold);
        ret.put("maxPotionSlot", this.maxPotionSlot);
        ret.put("numEnergyGainPerTurn", this.numEnergyGainPerTurn);
        ret.put("numCardsDrawPerTurn", this.numCardsDrawPerTurn);
        ret.put("maxHandSize", this.maxHandSize);
        ret.put("cardPurgeServiceCount", this.cardPurgeServiceCount);
        List<String> deck = new ArrayList<>();
        List<Object> cardSaves = new ArrayList<>();
        for (AbstractCard card : this.masterDeck) {
            InstanceDataSaver cardSaver = InstanceDataSaver.getSaver(card.getClass());
            deck.add(card.id);
            if (cardSaver != null) {
                cardSaves.add(cardSaver.onSave(card));
            }
        }
        ret.put("deck", deck);
        ret.put("cardSaves", cardSaves);
        List<String> relics = new ArrayList<>();
        List<Object> relicSaves = new ArrayList<>();
        for (AbstractRelic relic : this.relics) {
            InstanceDataSaver relicSaver = InstanceDataSaver.getSaver(relic.getClass());
            relics.add(relic.id);
            if (relicSaver != null) {
                relicSaves.add(relicSaver.onSave(relic));
            }
        }
        ret.put("relics", relics);
        ret.put("relicSaves", relicSaves);
        List<String> potions = new ArrayList<>();
        List<Object> potionSaves = new ArrayList<>();
        for (AbstractPotion potion : this.potions) {
            InstanceDataSaver potionSaver = InstanceDataSaver.getSaver(potion.getClass());
            potions.add(potion.id);
            if (potionSaver != null) {
                potionSaves.add(potionSaver.onSave(potion));
            }
        }
        ret.put("potions", potions);
        ret.put("potionSaves", potionSaves);
        return ret;
    }

    @Override
    public void onLoad(Map<String, Object> data) {
        if (data != null) {
            this.currentHealth = (int) data.get("hp");
            this.maxHealth = (int) data.get("maxHp");
            this.gold = (int) data.get("gold");
            this.maxPotionSlot = (int) data.get("maxPotionSlot");
            this.numEnergyGainPerTurn = (int) data.get("numEnergyGainPerTurn");
            this.numCardsDrawPerTurn = (int) data.get("numCardsDrawPerTurn");
            this.maxHandSize = (int) data.getOrDefault("maxHandSize", 10);
            this.cardPurgeServiceCount = (int) data.getOrDefault("cardPurgeServiceCount", 0);
            for (String cardId : (List<String>) data.get("deck")) {
                this.masterDeck.add(CardLibrary.getCard(cardId).makeCopy());
            }
            List<Object> cardSaves = (List<Object>) data.get("cardSaves");
            int i = 0;
            for (AbstractCard card : this.masterDeck) {
                InstanceDataSaver.getSaver(card.getClass()).onLoad(card, cardSaves.get(i));
                i++;
            }
            for (String relicId : (List<String>) data.get("relics")) {
                this.relics.add(RelicLibrary.getRelic(relicId).makeCopy());
            }
            List<Object> relicSaves = (List<Object>) data.get("relicSaves");
            i = 0;
            for (AbstractRelic relic : this.relics) {
                InstanceDataSaver.getSaver(relic.getClass()).onLoad(relic, relicSaves.get(i));
                i++;
            }
            for (String potionId : (List<String>) data.get("potions")) {
                this.potions.add(PotionLibrary.getPotion(potionId));
            }
            i = 0;
            List<Object> potionSaves = (List<Object>) data.get("potionSaves");
            for (AbstractPotion potion : this.potions) {
                InstanceDataSaver.getSaver(potion.getClass()).onLoad(potion, potionSaves.get(i));
                i++;
            }
        } else {
            for (String cardId : getStartingDeck()) {
                this.masterDeck.add(CardLibrary.getCard(cardId).makeCopy());
            }
            for (String relicId : getStartingRelics()) {
                this.relics.add(RelicLibrary.getRelic(relicId).makeCopy());
            }
        }
    }

    @Override
    public void onRemove() {
        super.onRemove();
        for (AbstractRelic relic :relics) {
            relic.onRemove();
        }
        for (AbstractCard card : masterDeck) {
            card.onRemove();
        }
        for (AbstractCard card : drawPile) {
            card.onRemove();
        }
        for (AbstractCard card : hand) {
            card.onRemove();
        }
        for (AbstractCard card : discardPile) {
            card.onRemove();
        }
        for (AbstractCard card : exhaustPile) {
            card.onRemove();
        }
        for (AbstractPotion potion : potions) {
            potion.onRemove();
        }
    }

    public boolean hasRelic(String id) {
        for (AbstractRelic relic : this.relics) {
            if (relic.id.equals(id)) {
                return true;
            }
        }
        return false;
    }
}
